 #define _BSD_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>

#include "config.h"
#include "zbd-cache.h"
#include "util/log.h"

extern struct cache_runtime cache_rt;
typedef uint64_t blockaddr_t ;
typedef int zoneid_t ;

static blockaddr_t* TraceMAP;

#define OFFLINE
#ifdef OFFLINE
/******************* Reuse Interval Utils *******************/
#define MMOD 100000003 // 哈希表大质数(建议比DIFFER_BLOCK大)
#define DIFFER_BLOCK 500000000 // 出现过的不同的块数
#define MAX_ACCRSS_LEN 1000000000 // trace 长度

static int* Pages_Head; // MMAP -> Pages_Head[DIFFER_BLOCK]
static int* Pages_Nxt;  // MMAP -> Pages_Nxt[TRACE_LEN]
int Cursor;
typedef struct hash_translate{
    /*
    int head[MMOD+5] , <- malloc
        pre[DIFFER_BLOCK+5] , <- MMAP
        mapval[DIFFER_BLOCK+5] , <- MMAP
        tp ;
         
    blockaddr_t val[DIFFER_BLOCK+5] ;
    */
    int* head;
    int* pre;
    int* mapval;
    int tp ;
         
    blockaddr_t *val;
    int (*find) ( void* , blockaddr_t ) ;
    int (*insert) ( void* , blockaddr_t ) ;
} transtab_t ;
transtab_t transtab ;

static int transtab_t_find( void *t_ , blockaddr_t addr ) {
    transtab_t* t = t_ ;
    int id = addr % MMOD ;
    for( int i = t->head[id] ; i ; i = t->pre[i] ) {
        if( t->val[i] == addr ){
            return t->mapval[i] ;
        }
    }
    return -1 ;
}

static int transtab_t_insert( void *t_ , blockaddr_t addr ) {
    transtab_t *t = t_ ;
    int id = addr % MMOD ;
    ++t->tp  ;
    t->pre[t->tp] = t->head[id] ;
    t->head[id] = t->tp ;
    t->val[t->tp] = addr ;
    t->mapval[t->tp] = t->tp ;
    return 0 ;
}

static int ri_utils_init( blockaddr_t *addrs , int reqCnt ){

    /* 初始化opt哈希表 */
    char hashmap_dir[128];
    sprintf(hashmap_dir, "%s-MAXMINRIGREEDY-trace_%d", config_trace_hashmap_dir_prefix, STT.traceId);

    if(access(hashmap_dir, 0) == -1){
        mkdir(hashmap_dir, 0x0777);
    }


    transtab.head = (int*)calloc(MMOD + 5, sizeof(int));
    if(transtab.head == NULL){
        log_err_sac("[%s] error: %s\n", __func__, strerror(errno));
        exit(EXIT_FAILURE);
    }

    char hashfile_pre_path[128], hashfile_mapval_path[128], hashfile_val_path[128];
    sprintf(hashfile_pre_path, "%s/pre", hashmap_dir);
    sprintf(hashfile_mapval_path, "%s/mapval", hashmap_dir);
    sprintf(hashfile_val_path, "%s/val", hashmap_dir);

    int prefd, mapvalfd, valfd;
    prefd = open(hashfile_pre_path, O_RDWR | O_CREAT);
    mapvalfd = open(hashfile_mapval_path, O_RDWR | O_CREAT);
    valfd = open(hashfile_val_path, O_RDWR | O_CREAT);
    if ( prefd < 0 || mapvalfd < 0 || valfd < 0){
        log_err_sac("Open TracefileMap failed: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    ftruncate(prefd, sizeof(int) * (DIFFER_BLOCK+5));
    ftruncate(mapvalfd, sizeof(int) * (DIFFER_BLOCK+5));
    ftruncate(valfd, sizeof(blockaddr_t) * (DIFFER_BLOCK+5));

    transtab.pre =      mmap(NULL, sizeof(int) * (DIFFER_BLOCK+5), PROT_READ | PROT_WRITE, MAP_SHARED, prefd, 0);
    transtab.mapval =   mmap(NULL, sizeof(int) * (DIFFER_BLOCK+5), PROT_READ | PROT_WRITE, MAP_SHARED, mapvalfd, 0);
    transtab.val =      mmap(NULL, sizeof(blockaddr_t) * (DIFFER_BLOCK+5), PROT_READ | PROT_WRITE, MAP_SHARED, valfd, 0);

    if (transtab.pre == (void *)-1 || transtab.mapval == (void *)-1 || transtab.val == (void *)-1)
    {
        log_err_sac("Create hashtabel mmap failed: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    /* 初始化Pages_Head[] */
    char pageshead_path[128], pagesnxt_path[128];
    sprintf(pageshead_path, "%s/pageshead", hashmap_dir);
    sprintf(pagesnxt_path, "%s/pagesnxt", hashmap_dir);
    int pagesheadfd = open(pageshead_path, O_RDWR | O_CREAT); 
    int pagesnxtfd = open(pagesnxt_path, O_RDWR | O_CREAT); 
    ftruncate(pagesheadfd, sizeof(int) * DIFFER_BLOCK);
    ftruncate(pagesnxtfd, sizeof(int) * reqCnt);

    Pages_Head = mmap(NULL, sizeof(int) * DIFFER_BLOCK, PROT_READ | PROT_WRITE, MAP_SHARED, pagesheadfd, 0);
    Pages_Nxt =  mmap(NULL, sizeof(int) * reqCnt, PROT_READ | PROT_WRITE, MAP_SHARED, pagesnxtfd, 0); 
    if (Pages_Head == (void *)-1 || Pages_Nxt == (void *)-1)
    {
        log_err_sac("Create hashtabel mmap failed: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    transtab.tp = 0 ;
    transtab.find = transtab_t_find ;
    transtab.insert = transtab_t_insert ;
    //memset( transtab.head , 0 , sizeof( transtab.head ) ) ; // calloc

    // head should init to be +inf , so that next[last] = inf 
    int tp =  reqCnt;
    for( int i = 0 ; i < DIFFER_BLOCK ; i ++ ){
        Pages_Head[i] = reqCnt ;
    }
    Cursor = -1 ; // cursor position of access list
    
    for( int i = reqCnt - 1 ; i >= 0 ; i -- ){
        int mappedaddr = transtab.find( &transtab , addrs[i] ) ;
        if( mappedaddr < 0 ){
            transtab.insert( &transtab , addrs[i] ) ;
            mappedaddr = transtab.find( &transtab , addrs[i] ) ;
        }
        Pages_Nxt[--tp] = Pages_Head[mappedaddr] ;
        Pages_Head[mappedaddr] = tp ;
    }


    return 0;
}

static int ri_utils_movnext( blockaddr_t addr ){
    int mappedaddr = transtab.find( &transtab , addr ) ;
    if( Pages_Head[mappedaddr] != Cursor + 1 ){ // check if the input addr match the saved info
        log_err_sac("%s: error\n", __func__);
        exit(EXIT_FAILURE);
    }
    Pages_Head[mappedaddr] = Pages_Nxt[ Pages_Head[mappedaddr] ] ;
    Cursor ++ ;
    return 0 ; // OK
}

static int query_now_ri( blockaddr_t addr ){
    int mappedaddr = transtab.find( &transtab , addr ) ;
    return Pages_Head[mappedaddr] - Cursor ;    
}

/******************* Reuse Interval Utils *******************/
#endif

int min_init()
{
    if(STT.zbd_drive_type != DM_SMR){
        log_err_sac("%s: MIN algorithm force to use DM-SMR drive.\n", __func__);
        return -1;
    }

    if(STT.workload_mode != 0x02){
        log_err_sac("%s: MIN algorithm only support write-only mode right now. \n", __func__);
        return -1;
    }

    char tracemap[128];
    sprintf(tracemap, "%s_%d", config_trace_seq_mmap_file_prefix, STT.traceId);
    int maxMapReq = 2000000000;
    
    int tracemapfd = open(tracemap, O_RDONLY);
    if (tracemapfd < 0)
    {
        // open trace file
        
        if ((tracemapfd = open(tracemap, O_RDWR | O_CREAT)) < 0)
        {
            log_err_sac("Open TracefileMap failed: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        ftruncate(tracemapfd, sizeof(uint64_t) * maxMapReq);

        TraceMAP = mmap(NULL, sizeof(uint64_t) * maxMapReq, PROT_READ | PROT_WRITE, MAP_SHARED, tracemapfd, 0);
        if (TraceMAP == (void *)-1)
        {
            log_err_sac("Create TraceMap failed: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        FILE *trace = fopen(TRACE_FILES[STT.traceId], "rt");
        if (trace == NULL)
        {
            log_err_sac("Open trace file failed: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        };

        char action;
        uint64_t tg_blk;
        int mask;
        uint64_t *wrtReq = TraceMAP;
        uint64_t scanReqCnt = 0;
        while (!feof(trace)) // && scanReqCnt < STT.presetReq)
        {
            int ret;
#ifdef TRACE_SYSTOR17
            ret = fscanf(trace, "%c %d %lu\n", &action, &mask, &tg_blk);
#else
            ret = fscanf(trace, "%d %d %lu\n", &action, &mask, &tg_blk);
#endif

            if (ret < 0)
            {
                log_err_sac("error while reading trace file.");
                break;
            }

#ifdef TRACE_SYSTOR17
            tg_blk /= BLKSIZE;
#endif
            tg_blk += STT.start_Blkoff;

            if (action == ACT_WRITE && (STT.workload_mode & 0x02))
            {
                if ((tg_blk / N_ZONEBLK) > N_ZONES)
                {
                    log_info_sac("[warning] func:%s, target block overflow. \n", __func__);
                    continue;
                }

                *wrtReq = tg_blk;
                wrtReq++;
                scanReqCnt++;
            }
        }

        fclose(trace);
        msync(TraceMAP, sizeof(uint64_t) * maxMapReq, MS_SYNC);
        munmap(TraceMAP, sizeof(uint64_t) * maxMapReq);
        close(tracemapfd);
        // reopen
        if ((tracemapfd = open(tracemap, O_RDONLY)) < 0)
        {
            log_err_sac("Open TracefileMap failed: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

    TraceMAP = mmap(NULL, sizeof(uint64_t) * maxMapReq, PROT_READ, MAP_SHARED, tracemapfd, 0);
    if (TraceMAP == (void *)-1)
    {
        log_err_sac("Create TraceMap failed: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    ri_utils_init(TraceMAP, STT.presetReq + (4*STT.n_cache_pages));
    return 0;
}

// addr : login addr , op : useless
int min_login( struct cache_page *page , int op ){
    return  ri_utils_movnext(page->tg_blk);
}

// call this function at every access , whether hit or not
// addr : accessed addr , op : useless
int min_hit( struct cache_page *page , int op ){
    return ri_utils_movnext(page->tg_blk);
}

// addr : logout addr , op : useless
int min_logout( struct cache_page *page , int op ){
    return 0;
}

// type : useless
int min_writeback_privi( int type ){
    if(cache_rt.header_free_page != NULL){
        log_info_sac("%s: Warning.\n", __func__);
        exit(EXIT_FAILURE);
    }

    static int last_best_zoneId = -1;
    int maxRI = 0;
    struct cache_page* page_maxRI = NULL;

    struct cache_page* page = cache_rt.pages;
    for (uint64_t i = 0; i < STT.n_cache_pages; page++, i++)
    {
        int ri = query_now_ri(page->tg_blk);
        if(ri > maxRI){
            maxRI = ri;
            page_maxRI = page;
        } else if (ri == maxRI && page->belong_zoneId == (uint32_t)last_best_zoneId){
            maxRI = ri;
            page_maxRI = page;
        }
    }

    int belong_zoneId = page_maxRI->belong_zoneId;
    int off = page_maxRI->tg_blk - (belong_zoneId * N_ZONEBLK);


    int dirty_pages = RMW(belong_zoneId, off, off);

    last_best_zoneId = belong_zoneId;
    return belong_zoneId ;
}
